package org.smoozik.app.common.partition.component;

import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import org.smoozik.app.common.partition.PartitionValues;
import org.smoozik.app.ihm.Constants;
import org.smoozik.services.musictheory.Figure;
import org.smoozik.services.musictheory.Partition;
import org.smoozik.services.musictheory.Token;
import org.smoozik.services.sequence.Sequence;
import org.smoozik.services.sequence.Song;

public class UISong {
	private Song song;

	private ArrayList<UIMeasure> measures;
	// private ArrayList<UIPartition> uiPartitions;
	private ArrayList<UIPartition> partitions;
	// HashMap<Partition, UIPartition> partitionMap;
	private ArrayList<UIMusicComponent> musicComponents;
	private HashMap<Token, UIMusicComponent> componentFinder;
	private ArrayList<Token> tokens;
	private HashMap<Sequence, ArrayList<Token>> sequenceTokensList;
	private HashMap<UIPartition, ArrayList<UIMeasure>> partitionMeasures;

	public UISong(Song song) {
		super();
		this.song = song;
		this.tokens = song.getTokens();
		init();
	}

	public ArrayList<UIMusicComponent> getMusicComponentsForSequence(
			Sequence sequence) {
		ArrayList<UIMusicComponent> components = new ArrayList<>();

		for (Token token : sequenceTokensList.get(sequence)) {
			components.add(componentFinder.get(token));
		}

		return components;
	}

	// public ArrayList<UIPartition> getPartitions() {
	// return uiPartitions;
	// }
	//
	// public void setPartitions(ArrayList<UIPartition> partitions) {
	// this.uiPartitions = partitions;
	// }

	public ArrayList<UIPartition> getPartitions() {
		return partitions;
	}

	public Song getSong() {
		return song;
	}

	public void setSong(Song song) {
		this.song = song;
	}

	public void setPartitions(ArrayList<UIPartition> partitions) {
		this.partitions = partitions;
	}

	public ArrayList<UIMeasure> getMeasures() {
		return measures;
	}

	public void setMeasures(ArrayList<UIMeasure> measures) {
		this.measures = measures;
	}

	public ArrayList<UIMusicComponent> getMusicComponents() {
		ArrayList<UIMusicComponent> musicComponents = new ArrayList<>();

		for (UIMeasure m : measures) {
			musicComponents.addAll(m.getMusicComponents());
		}

		return musicComponents;
	}

	public void init() {
		// Set UIPartitions to the components
		initPartitions();

		// measures creation
		createMeasures();

		// measures feeding
		feedMeasures();

		// measures feeding
		feedRemanant();

		// Rythm forms setting
		initMeasures();

		// complete the sequence
		completeSequence();

		// Rythm forms setting
		setPartitionsToMeasures();
	}

	// create the uipartition
	// create the map between partitions and uiPartitions
	// create the map between partitions and tokens of a partition
	private void initPartitions() {
		Figure f = null;
		Partition partition = null;
		UIPartition uiPartition = null;
		int height = Constants.DEFAULT_HEIGHT;
		HashMap<Partition, UIPartition> partitionMap = new HashMap<>();
		partitions = new ArrayList<>();

		for (Token t : tokens) {
			f = t.getFigure();
			partition = f.getPartition();

			if (partition == null) {
				System.err
						.println("partition null dans un token de sequence : "
								+ t.toString());
			}

			// create the uiPartition
			// create the token list for the partition
			if (partitions.contains(partition)) {
				uiPartition = (UIPartition) partition;
			} else {
				uiPartition = partitionMap.get(partition);
			}
			if (uiPartition == null) {
				// uiPartition creation
				uiPartition = new UIPartition(partition);

				partitionMap.put(partition, uiPartition);
				partitions.add(uiPartition);
			}

			// replace the partition by the uiPartition
			f.setPartition(uiPartition);
		}

		if (partitions != null && partitions.size() > 0) {
			Collections.sort(partitions);
		}

		for (UIPartition p : partitions) {
			p.setHeight(height);
			height += 10 * PartitionValues.getWidth();
		}
	}

	private void createMeasures() {
		int nbTimesByMeasure = song.getNbTimesByMeasure();
		double last = song.getLastPlaceFromTokenList();
		UIMeasure measure = null;

		measures = new ArrayList<>();

		// measures creation
		int nbMeasures = (int) last / nbTimesByMeasure;
		if (last % nbTimesByMeasure > 0) {
			nbMeasures++;
		}
		for (int i = 0; i < nbMeasures; i++) {
			measure = new UIMeasure(nbTimesByMeasure, i);
			measure.setPartitions(partitions);
			measures.add(measure);
		}
	}

	// add the tokens for each measure
	// saves the origin of the token in an list for each sequence
	private void feedMeasures() {
		int nbTimesByMeasure = song.getNbTimesByMeasure();
		double place = 0;
		int numMeasure = 0;
		UIMeasure measure = null;
		ArrayList<Token> sequenceTokens;
		Token newToken = null;
		sequenceTokensList = new HashMap<>();

		for (Sequence sequence : song.getSequences()) {
			sequenceTokens = new ArrayList<>();
			sequenceTokensList.put(sequence, sequenceTokens);
			for (Token t : sequence.getTokens()) {
				place = t.getPlace();
				numMeasure = (int) (place / nbTimesByMeasure);
				measure = measures.get(numMeasure);
				newToken = new Token(t);
				measure.addToken(newToken);
				sequenceTokens.add(newToken);
			}
		}
	}

	private void feedRemanant() {
		ArrayList<Token> remanantTokens = null;

		for (UIMeasure m : measures) {
			if (remanantTokens != null) {
				m.addTokens(remanantTokens);
			}
			remanantTokens = m.extractRemanantTokens();
		}
	}

	private void initMeasures() {
		for (UIMeasure m : measures) {
			m.init();
		}
	}

	public void completeSequence() {
		musicComponents = new ArrayList<>();
		componentFinder = new HashMap<>();

		for (UIMeasure m : measures) {
			musicComponents.addAll(m.getMusicComponents());
			componentFinder.putAll(m.getComponentFinder());
		}
	}

	private void setPartitionsToMeasures() {
		int partStart = 150;
		int partLength = partStart;
		int measurelength;
		int pad = (partitions.size() * 10 + 2) * PartitionValues.getWidth();
		int updateHeight = 0;
		HashMap<UIPartition, UIPartition> map = null;
		partitionMeasures = new HashMap<>();
		ArrayList<UIMeasure> measuresPart = null;

		for (UIMeasure measure : this.measures) {
			measurelength = measure.getLength();
			// TODO get partition length
			if (partLength != partStart
					&& measurelength + partLength > Constants.DEFAULT_LENGTH) {
				measure.setStart(partStart);
				updateHeight += pad;
				partLength = partStart + measurelength;
				map = measure.splitPartitions(updateHeight);
				measure.updatePartitions(map);
				partitions.addAll(measure.getPartitions());
			} else {
				if (map != null) {
					measure.updatePartitions(map);
				}
				measure.setStart(partLength);
				partLength += measurelength;
			}

			// Add the measure to the partition map
			measuresPart = partitionMeasures
					.get(measure.getPartitions().get(0));
			if (measuresPart == null) {
				measuresPart = new ArrayList<>();
				partitionMeasures.put(measure.getPartitions().get(0),
						measuresPart);
			}

			measuresPart.add(measure);
		}

		// sets the partition width to the max of the uiPartitions
		int width = getWidth();

		for (UIPartition part : partitions) {
			part.setLength(width);
		}
	}

	public void paint(Graphics g, Rectangle rectangle) {
		// System.err.println("painting sequence");
		boolean first = true;
		ArrayList<UIMeasure> measuresPart = null;
		UIMusicComponent next;
		UIMusicComponent previous;
		Token t = null;
		UIMeasure measure = null;

		// paint the uiPartitions
		for (UIPartition p : partitions) {
			// if (rectangle.y <= p.getHeight()
			// && p.getHeight() <= rectangle.y + rectangle.height) {
			p.paint(g, first);
			measuresPart = partitionMeasures.get(p);
			if (measuresPart != null) {
				// paint the measures
				for (UIMeasure m : measuresPart) {
					m.paint(g);

					// paint the links between the notes
					for (UIMusicComponent c : m.getMusicComponents()) {
						t = c.getToken();
						if (t.getNext() != null) {
							next = componentFinder.get(t.getNext());
							paintNextLink(g, c, next);
						}
						if (t.getPrevious() != null) {
							previous = componentFinder.get(t.getPrevious());
							paintPreviousLink(g, previous, c);
						}
					}
				}
			}
			// }
			first = false;
		}

		// paint the end bar
		if (measures != null && measures.size() > 0) {
			measure = measures.get(measures.size() - 1);
			int end = measure.getEnd();

			for (UIPartition p : measure.getPartitions()) {
				int height = p.getHeight();
				g.fillRect(end, height, PartitionValues.getWidth() / 2,
						4 * PartitionValues.getWidth() + 1);
				g.drawLine(end - PartitionValues.getWidth() / 2, height, end
						- PartitionValues.getWidth() / 2, height + 4
						* PartitionValues.getWidth());
			}
		}
	}

	private void paintPreviousLink(Graphics g, UIMusicComponent c1,
			UIMusicComponent c2) {
		int y1 = (int) (c1.getY() + c1.getHeight());
		int y2 = (int) (c2.getY() + c2.getHeight());
		int x1 = (int) (c1.getX() + PartitionValues.getWidth() / 2);
		int x2 = (int) (c2.getX() + PartitionValues.getWidth() / 2);

		if (y1 != y2) {
			y1 = y2;
			x1 = x2 - PartitionValues.getPad();
		}

		// g.setColor(Color.RED);
		// g.drawLine(x1, y1, x2, y2);
		g.drawArc(x1, y1, x2 - x1, PartitionValues.getWidth(), 200, 140);
		// g.drawRect(x1, y1, x2 - x1, PartitionValues.getWidth());
		g.drawArc(x1, y1, x2 - x1, PartitionValues.getWidth() + 1, 200, 140);
		// g.setColor(Color.BLACK);
	}

	// the next link is painted only when necessary
	// only when the two components are not on the same line
	private void paintNextLink(Graphics g, UIMusicComponent c1,
			UIMusicComponent c2) {
		int y1 = (int) (c1.getY() + c1.getHeight());
		int y2 = (int) (c2.getY() + c2.getHeight());
		int x1 = (int) (c1.getX() + PartitionValues.getWidth() / 2);
		int x2 = (int) (c2.getX() + PartitionValues.getWidth() / 2);

		if (y1 != y2) {
			y2 = y1;
			x2 = x1 + PartitionValues.getPad();
			// g.setColor(Color.BLUE);
			// g.drawLine(x1, y1, x2, y2);
			g.drawArc(x1, y1, x2 - x1, PartitionValues.getWidth(), 200, 140);
			g.drawArc(x1, y1, x2 - x1, PartitionValues.getWidth() + 1, 200, 140);
			// g.setColor(Color.BLACK);
		}
	}

	public int getWidth() {
		int max = 50;
		int end;

		if (measures != null) {
			for (UIMeasure m : measures) {
				end = m.getEnd();
				if (end > max) {
					max = end;
				}
			}
		}

		return max;
	}

	public int getHeight() {
		if (partitions != null) {
			UIPartition part = partitions.get(partitions.size() - 1);
			return part.getHeight() + 20 * PartitionValues.getWidth();
		} else {
			return 50;
		}
	}

}
